# coding=utf-8
# __author__ = "James Zhang"
# !/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function, division

import base64
import logging
import string
from datetime import datetime, timedelta

from core.conf import settings
from core.sessions import exceptions
from core.utils import timezone, serializers
from core.utils.crypto import force_bytes, constant_time_compare
from core.utils.crypto import salted_hmac, get_random_string

SESSION_COOKIE_AGE = settings.SESSION_COOKIE_AGE
SESSION_EXPIRE_AT_BROWSER_CLOSE = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE

VALID_KEY_CHARS = string.ascii_lowercase + string.digits


class CreateError(Exception):
    """
        用于引发session.save()的异常事件.详细可以看SessionBase.save()
    """
    pass


class SessionBase(object):
    """
    self._session           ===> Session数据, 一个python字典对象
    self._session_key       ===> Session key(由浏览器传送过来的),
    self._session_cache     ===> Session数据的缓存
    self.accessed
    self.modified
    self.serializer         ===> 序列化方法
    """
    TEST_COOKIE_NAME = 'testcookie'
    TEST_COOKIE_VALUE = 'worked'

    def __init__(self, session_key=None):
        self._session_key = session_key
        self.accessed = False   # 是否已进入
        self.modified = False   # 是否已编辑
        self.serializer = serializers.JSONSerializer

    def __contains__(self, key):
        """
        当使用in, not in对象的时候调用此方法
        :param key:
        :return:
        """
        return key in self._session

    def __getitem__(self, key):
        """返回键对应的值, 如: f['item']时调用此方法

        :param key:
        :return:
        """
        return self._session[key]

    def __setitem__(self, key, value):
        """设置给定的值, 如: f['item'] = value

        :param key:
        :param value:
        :return:
        """
        self._session[key] = value
        self.modified = True

    def __delitem__(self, key):
        """删除给定键的值, 如: del f['item']

        :param key:
        :return:
        """
        del self._session[key]
        self.modified = True

    def get(self, key, default=None):
        return self._session.get(key, default)

    def pop(self, key, *args):
        self.modified = self.modified or key in self._session_key
        return self._session.pop(key, *args)

    def setdefault(self, key, value):
        """如果self._session中有key这个值,则直接返回,没有则设置self._session[key]=value

        :param key:
        :param value:
        :return:
        """
        if key in self._session:
            return self._session[key]
        else:
            self.modified = True
            self._session[key] = value
            return value

    def set_test_cookie(self):
        self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE

    def test_cookie_worked(self):
        return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE

    def delete_test_cookie(self):
        del self[self.TEST_COOKIE_NAME]

    def _hash(self, value):
        """加密

        :param value:
        :return:
        """
        key_salt = "tornado.backends.sessions" + self.__class__.__name__
        return salted_hmac(key_salt, value).hexdigest()

    def encode(self, session_dict):
        """加密session_dict:
                1. 将session_dict数据序列化
                2. 将session_dict复制一份,第一份使用hash加密,并用:连接
                3. 使用base64转码.

        :param session_dict:
        :return:
        """
        serialized = self.serializer().dumps(session_dict)
        _hash = self._hash(serialized)
        return base64.b64encode(_hash.encode() + b":" + serialized).decode("ascii")

    def decode(self, session_data):
        """解密session_data
                1. 使用base64对session_data进行解码, 并分割出两个字符串
                2. 使用self._hash加密两个分割的字符串并进行比对, 若比对失败,则抛出异常(Session data corrupted)
                3. 若比对OK,返回解密后的session_data
        :param session_data:
        :return:
        """
        encoded_data = base64.b64decode(force_bytes(session_data))
        try:
            _hash, serialized = encoded_data.split(b":", 1)
            expected_data = self._hash(serialized)
            if not constant_time_compare(_hash.decode(), expected_data):
                raise exceptions.SuspiciousSession("Session data corrupted")
            else:
                return self.serializer().loads(serialized)
        except Exception as e:
            if isinstance(e, exceptions.SuspiciousOperation):
                logger = logging.getLogger("tornado.security.%s" % e.__class__.__name__)
                logger.warning(force_bytes(e))
        return {}

    def update(self, dict_):
        """
        更新数据
        :param dict_:
        :return:
        """
        self._session.update(dict_)
        self.modified = True

    def has_key(self, key):
        """
        判断是否有key
        :param key:
        :return:
        """
        return key in self._session

    def keys(self):
        return self._session.keys()

    def values(self):
        return self._session.keys()

    def items(self):
        return self._session.items()

    def iterkeys(self):
        return self._session.iterkeys()

    def itervalues(self):
        return self._session.itervalues()

    def iteritems(self):
        return self._session.iteritems()

    def clear(self):
        self._session_cache = {}
        self.accessed = True
        self.modified = True

    def is_empty(self):
        """
        判断session是否为空
        :return:
        """
        try:
            return not bool(self._session_key) and not self._session_cache
        except AttributeError:
            return True

    def _get_new_session_key(self):
        while True:
            session_key = get_random_string(32, VALID_KEY_CHARS)
            if not self.exists(session_key):
                break
        return session_key

    def _get_or_create_session_key(self):
        if self._session_key is None:
            self._session_key = self._get_new_session_key()
        return self._session_key

    def _get_session_key(self):
        return self._session_key

    session_key = property(_get_session_key)

    def _get_session(self, no_load=False):
        self.accessed = True
        try:
            return self._session_cache
        except AttributeError:
            if self.session_key is None or no_load:
                self._session_cache = {}
            else:
                self._session_cache = self.load()
        return self._session_cache

    _session = property(_get_session)

    def get_expiry_age(self, **kwargs):
        try:
            modification = kwargs['modification']
        except KeyError:
            modification = timezone.now()
        try:
            expiry = kwargs['expiry']
        except KeyError:
            expiry = self.get('_session_expiry')
        if not expiry:
            return SESSION_COOKIE_AGE
        if not isinstance(expiry, datetime):
            return expiry
        delta = expiry - modification
        return delta.days * 86400 + delta.seconds

    def get_expiry_date(self, **kwargs):
        try:
            modification = kwargs['modification']
        except KeyError:
            modification = timezone.now()
        try:
            expiry = kwargs['expiry']
        except KeyError:
            expiry = self.get('_session_expiry')
        if isinstance(expiry, datetime):
            return expiry
        if not expiry:
            expiry = SESSION_COOKIE_AGE
        return modification + timedelta(seconds=expiry)

    def set_expiry(self, value):
        if value is None:
            try:
                del self['_session_expiry']
            except KeyError:
                pass
            return
        if isinstance(value, timedelta):
            value = timezone.now() + value
        self['_session_expiry'] = value

    def get_expire_at_brower_close(self):
        """
        """
        if self.get('_session_expiry') is None:
            return SESSION_EXPIRE_AT_BROWSER_CLOSE
        return self.get('_session_expiry') == 0

    def flush(self):
        self.clear()
        self.delete()
        self._session_key = True

    def cycle_key(self):
        data = self._session_cache
        key = self.session_key
        self.create()
        self._session_cache = data
        self.delete(key)

    def create(self):
        raise NotImplementedError('subclasses of SessionBase must provide a create() method')

    def load(self):
        """
        Loads the session data and returns a dictionary.
        """
        raise NotImplementedError('subclasses of SessionBase must provide a load() method')

    def save(self, must_create=False):
        """
        保存会话数据。如果must_create”为True，创建一个新的session对象
        否则引发一个Createerror异常）。
        另外,save()可以更新现有对象具有相同的键。
        :param must_create:
        :return:
        """
        raise NotImplementedError('subclasses of SessionBase must provide a save() method')

    def delete(self):
        raise NotImplementedError('subclasses of SessionBase must provide a delete() method')

    def exists(self, session_key):
        """子类需要重写此方法
        :param session_key:
        """
        raise NotImplementedError('subclasses of SessionBase must provide an exists() method')

    @classmethod
    def clear_expired(cls):
        raise NotImplementedError('This backend does not support clear_expired().')
